(function(root, factory) {

	'use strict';

	// CommonJS module is defined
	if (typeof module !== 'undefined' && module.exports) {
		module.exports = factory(require('jquery'));
	}
	// AMD module is defined
	else if (typeof define === 'function' && define.amd) {
		define([ 'jquery' ], function($) {
			return factory($);
		});
	} else {
		factory(root.jQuery);
	}

}
		(
				this,
				function($) {

					'use strict';
					// jshint laxcomma: true

					/*
					 * TYPEAHEAD PUBLIC CLASS DEFINITION
					 * =================================
					 */

					var Typeahead = function(element, options) {
						this.$element = $(element);
						this.options = $.extend({}, $.fn.typeahead.defaults,
								options);
						this.matcher = this.options.matcher || this.matcher;
						this.sorter = this.options.sorter || this.sorter;
						this.select = this.options.select || this.select;
						this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect
								: true;
						this.highlighter = this.options.highlighter
								|| this.highlighter;
						this.render = this.options.render || this.render;
						this.updater = this.options.updater || this.updater;
						this.displayText = this.options.displayText
								|| this.displayText;
						this.source = this.options.source;
						this.delay = this.options.delay;
						this.$menu = $(this.options.menu);
						this.$appendTo = this.options.appendTo ? $(this.options.appendTo)
								: null;
						this.shown = false;
						this.listen();
						this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' ? this.options.showHintOnFocus
								: false;
						this.afterSelect = this.options.afterSelect;
						this.addItem = false;
					};

					Typeahead.prototype = {

						constructor : Typeahead,

						select : function() {
							var val = this.$menu.find('.active').data('value');
							this.$element.data('active', val);
							if (this.autoSelect || val) {
								var newVal = this.updater(val);
								this.$element.val(
										this.displayText(newVal) || newVal)
										.change();
								this.afterSelect(newVal);
							}
							return this.hide();
						},

						updater : function(item) {
							return item;
						},

						setSource : function(source) {
							this.source = source;
						},

						show : function() {
							var pos = $.extend({}, this.$element.position(), {
								height : this.$element[0].offsetHeight
							}), scrollHeight;

							scrollHeight = typeof this.options.scrollHeight == 'function' ? this.options.scrollHeight
									.call()
									: this.options.scrollHeight;

							(this.$appendTo ? this.$menu
									.appendTo(this.$appendTo) : this.$menu
									.insertAfter(this.$element)).css({
								top : pos.top + pos.height + scrollHeight,
								left : pos.left
							}).show();

							this.shown = true;
							return this;
						},

						hide : function() {
							this.$menu.hide();
							this.shown = false;
							return this;
						},

						lookup : function(query) {
							var items;
							if (typeof (query) != 'undefined' && query !== null) {
								this.query = query;
							} else {
								this.query = this.$element.val() || '';
							}

							if (this.query.length < this.options.minLength) {
								return this.shown ? this.hide() : this;
							}

							var worker = $.proxy(function() {

								if ($.isFunction(this.source))
									this.source(this.query, $.proxy(
											this.process, this));
								else if (this.source) {
									this.process(this.source);
								}
							}, this);

							clearTimeout(this.lookupWorker);
							this.lookupWorker = setTimeout(worker, this.delay);
						},

						process : function(items) {
							var that = this;

							items = $.grep(items, function(item) {
								return that.matcher(item);
							});

							items = this.sorter(items);

							if (!items.length && !this.options.addItem) {
								return this.shown ? this.hide() : this;
							}

							if (items.length > 0) {
								this.$element.data('active', items[0]);
							} else {
								this.$element.data('active', null);
							}

							// Add item
							if (this.options.addItem) {
								items.push(this.options.addItem);
							}

							if (this.options.items == 'all') {
								return this.render(items).show();
							} else {
								return this.render(
										items.slice(0, this.options.items))
										.show();
							}
						},

						matcher : function(item) {
							var it = this.displayText(item);
							return ~it.toLowerCase().indexOf(
									this.query.toLowerCase());
						},

						sorter : function(items) {
							var beginswith = [], caseSensitive = [], caseInsensitive = [], item;

							while ((item = items.shift())) {
								var it = this.displayText(item);
								if (!it.toLowerCase().indexOf(
										this.query.toLowerCase()))
									beginswith.push(item);
								else if (~it.indexOf(this.query))
									caseSensitive.push(item);
								else
									caseInsensitive.push(item);
							}

							return beginswith.concat(caseSensitive,
									caseInsensitive);
						},

						highlighter : function(item) {
							var html = $('<div></div>');
							var query = this.query;
							var i = item.toLowerCase().indexOf(
									query.toLowerCase());
							var len, leftPart, middlePart, rightPart, strong;
							len = query.length;
							if (len === 0) {
								return html.text(item).html();
							}
							while (i > -1) {
								leftPart = item.substr(0, i);
								middlePart = item.substr(i, len);
								rightPart = item.substr(i + len);
								strong = $('<strong></strong>')
										.text(middlePart);
								html.append(document.createTextNode(leftPart))
										.append(strong);
								item = rightPart;
								i = item.toLowerCase().indexOf(
										query.toLowerCase());
							}
							return html.append(document.createTextNode(item))
									.html();
						},

						render : function(items) {
							var that = this;
							var self = this;
							var activeFound = false;
							items = $(items).map(function(i, item) {
								var text = self.displayText(item);
								i = $(that.options.item).data('value', item);
								i.find('a').html(that.highlighter(text));
								if (text == self.$element.val()) {
									i.addClass('active');
									self.$element.data('active', item);
									activeFound = true;
								}
								return i[0];
							});

							if (this.autoSelect && !activeFound) {
								items.first().addClass('active');
								this.$element.data('active', items.first()
										.data('value'));
							}
							this.$menu.html(items);
							return this;
						},

						displayText : function(item) {
							return item.name || item;
						},

						next : function(event) {
							var active = this.$menu.find('.active')
									.removeClass('active'), next = active
									.next();

							if (!next.length) {
								next = $(this.$menu.find('li')[0]);
							}

							next.addClass('active');
						},

						prev : function(event) {
							var active = this.$menu.find('.active')
									.removeClass('active'), prev = active
									.prev();

							if (!prev.length) {
								prev = this.$menu.find('li').last();
							}

							prev.addClass('active');
						},

						listen : function() {
							this.$element
									.on('focus', $.proxy(this.focus, this)).on(
											'blur', $.proxy(this.blur, this))
									.on('keypress',
											$.proxy(this.keypress, this)).on(
											'keyup', $.proxy(this.keyup, this));

							if (this.eventSupported('keydown')) {
								this.$element.on('keydown', $.proxy(
										this.keydown, this));
							}

							this.$menu.on('click', $.proxy(this.click, this))
									.on('mouseenter', 'li',
											$.proxy(this.mouseenter, this)).on(
											'mouseleave', 'li',
											$.proxy(this.mouseleave, this));
						},

						destroy : function() {
							this.$element.data('typeahead', null);
							this.$element.data('active', null);
							this.$element.off('focus').off('blur').off(
									'keypress').off('keyup');

							if (this.eventSupported('keydown')) {
								this.$element.off('keydown');
							}

							this.$menu.remove();
						},

						eventSupported : function(eventName) {
							var isSupported = eventName in this.$element;
							if (!isSupported) {
								this.$element
										.setAttribute(eventName, 'return;');
								isSupported = typeof this.$element[eventName] === 'function';
							}
							return isSupported;
						},

						move : function(e) {
							if (!this.shown)
								return;

							switch (e.keyCode) {
							case 9: // tab
							case 13: // enter
							case 27: // escape
								e.preventDefault();
								break;

							case 38: // up arrow
								// with the shiftKey (this is actually the
								// left parenthesis)
								if (e.shiftKey)
									return;
								e.preventDefault();
								this.prev();
								break;

							case 40: // down arrow
								// with the shiftKey (this is actually the
								// right parenthesis)
								if (e.shiftKey)
									return;
								e.preventDefault();
								this.next();
								break;
							}

							e.stopPropagation();
						},

						keydown : function(e) {
							this.suppressKeyPressRepeat = ~$.inArray(e.keyCode,
									[ 40, 38, 9, 13, 27 ]);
							if (!this.shown && e.keyCode == 40) {
								this.lookup();
							} else {
								this.move(e);
							}
						},

						keypress : function(e) {
							if (this.suppressKeyPressRepeat)
								return;
							this.move(e);
						},

						keyup : function(e) {
							switch (e.keyCode) {
							case 40: // down arrow
							case 38: // up arrow
							case 16: // shift
							case 17: // ctrl
							case 18: // alt
								break;

							case 9: // tab
							case 13: // enter
								if (!this.shown)
									return;
								this.select();
								break;

							case 27: // escape
								if (!this.shown)
									return;
								this.hide();
								break;
							default:
								this.lookup();
							}

							e.stopPropagation();
							e.preventDefault();
						},

						focus : function(e) {
							if (!this.focused) {
								this.focused = true;
								if (this.options.showHintOnFocus) {
									this.lookup('');
								}
							}
						},

						blur : function(e) {
							this.focused = false;
							if (!this.mousedover && this.shown)
								this.hide();
						},

						click : function(e) {
							e.stopPropagation();
							e.preventDefault();
							this.select();
							this.$element.focus();
						},

						mouseenter : function(e) {
							this.mousedover = true;
							this.$menu.find('.active').removeClass('active');
							$(e.currentTarget).addClass('active');
						},

						mouseleave : function(e) {
							this.mousedover = false;
							if (!this.focused && this.shown)
								this.hide();
						}

					};

					/*
					 * TYPEAHEAD PLUGIN DEFINITION ===========================
					 */

					var old = $.fn.typeahead;

					$.fn.typeahead = function(option) {
						var arg = arguments;
						if (typeof option == 'string' && option == 'getActive') {
							return this.data('active');
						}
						return this
								.each(function() {
									var $this = $(this), data = $this
											.data('typeahead'), options = typeof option == 'object'
											&& option;
									if (!data)
										$this.data('typeahead',
												(data = new Typeahead(this,
														options)));
									if (typeof option == 'string') {
										if (arg.length > 1) {
											data[option].apply(data,
													Array.prototype.slice.call(
															arg, 1));
										} else {
											data[option]();
										}
									}
								});
					};

					$.fn.typeahead.defaults = {
						source : [],
						items : 8,
						menu : '<ul class="typeahead dropdown-menu" role="listbox"></ul>',
						item : '<li><a href="#" role="option"></a></li>',
						minLength : 1,
						scrollHeight : 0,
						autoSelect : true,
						afterSelect : $.noop,
						addItem : false,
						delay : 0
					};

					$.fn.typeahead.Constructor = Typeahead;

					/*
					 * TYPEAHEAD NO CONFLICT ===================
					 */

					$.fn.typeahead.noConflict = function() {
						$.fn.typeahead = old;
						return this;
					};

					/*
					 * TYPEAHEAD DATA-API ==================
					 */

					$(document).on('focus.typeahead.data-api',
							'[data-provide="typeahead"]', function(e) {
								var $this = $(this);
								if ($this.data('typeahead'))
									return;
								$this.typeahead($this.data());
							});

				}));
